﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Reflection;
using System.Windows;
using System.Windows.Input;
using CredentialsManager.Common;
using CredentialsManager.Helpers;
using CredentialsManager.Properties;
using MVVMFoundation;

namespace CredentialsManager.ViewModels
{
    public class ManagementViewModel : WorkspaceViewModelBase
    {
        // TODO: Refactor this class - it's about 185 lines too long

        #region Private classes

        private class RoleUserInput
        {
            public string Application { get; set; }
            public IList<string> SelectedRoles { get; set; }
            public IList<string> SelectedUsers { get; set; }
        }

        #endregion

        #region Private member variables

        private BackgroundWorker m_RefreshWorker;
        private BackgroundWorker m_SetupWorker;

        private BackgroundWorker m_AssignUsersWorker;
        private BackgroundWorker m_RemoveUsersWorker;
        private BackgroundWorker m_RemoveAllRolesWorker;
        private BackgroundWorker m_RemoveAllUsersWorker;

        private RelayCommand m_AssignUsersToRolesCommand;
        private RelayCommand m_RemoveUsersFromRolesCommand;
        private RelayCommand m_RemoveAllRolesFromUsersCommand;
        private RelayCommand m_RemoveAllUsersFromRolesCommand;

        private string m_SelectedApplication;

        private ApplicationListViewModel m_ApplicationListViewModel;
        private UserListViewModel m_UserListViewModel;
        private RoleListViewModel m_RoleListViewModel;
        private RolesForUserViewModel m_RolesForUserViewModel;
        private UsersInRoleViewModel m_UsersInRoleViewModel;

        #endregion

        #region Constructors

        public ManagementViewModel(string displayName)
            : base()
        {
            this.DisplayName = displayName;

            m_SetupWorker = new BackgroundWorker();
            m_SetupWorker.Initialise(m_SetupWorker_DoWork, 
                m_SetupWorker_RunWorkerCompleted, null, false);

            m_RefreshWorker = new BackgroundWorker();
            m_RefreshWorker.Initialise(m_RefreshWorker_DoWork,
                m_RefreshWorker_RunWorkerCompleted, m_Worker_ProgressChanged, false);

            m_AssignUsersWorker = new BackgroundWorker();
            m_AssignUsersWorker.Initialise(m_AssignUsersWorker_DoWork,
                m_Worker_RunWorkerCompleted, m_Worker_ProgressChanged, false);

            m_RemoveUsersWorker = new BackgroundWorker();
            m_RemoveUsersWorker.Initialise(m_RemoveUsersWorker_DoWork,
                m_Worker_RunWorkerCompleted, m_Worker_ProgressChanged, false);

            m_RemoveAllRolesWorker = new BackgroundWorker();
            m_RemoveAllRolesWorker.Initialise(m_RemoveAllRolesWorker_DoWork,
                m_Worker_RunWorkerCompleted, m_Worker_ProgressChanged, false);

            m_RemoveAllUsersWorker = new BackgroundWorker();
            m_RemoveAllUsersWorker.Initialise(m_RemoveAllUsersWorker_DoWork,
                m_Worker_RunWorkerCompleted, m_Worker_ProgressChanged, false);

            this.IsLoading = true;
            this.ProgressMessage = Resources.ValidatingServiceProgressMessage;
        }

        public ManagementViewModel()
            : this(Properties.Resources.ManagementViewModelDefaultDisplayName)
        { }

        #endregion

        #region Background worker handlers

        private void m_SetupWorker_DoWork(object sender, DoWorkEventArgs e)
        {
            var applicationListViewModel = new ApplicationListViewModel();
            e.Result = applicationListViewModel;
        }

        private void m_SetupWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            if (e.Error != null)
            {
                this.IsLoading = false;
                ErrorHandler.HandleNonSpecificWCFException(e.Error, this.DisplayName);
                Mediator.NotifyColleagues<bool>(MediatorMessages.SelectServiceViewModel, true);
            }
            else
            {
                Debug.Assert(!e.Cancelled);
                this.IsLoading = false;
                var applicationListViewModel = e.Result as ApplicationListViewModel;
                Debug.Assert(applicationListViewModel != null);
                this.ApplicationListViewModel = applicationListViewModel;
            }          
        }

        private void m_RefreshWorker_DoWork(object sender, DoWorkEventArgs e)
        {
            BackgroundWorker backgroundWorker = sender as BackgroundWorker;
            Debug.Assert(backgroundWorker != null);

            string application = e.Argument as string;
            Debug.Assert(!string.IsNullOrEmpty(application));

            backgroundWorker.ReportProgress(0, Resources.RetrievingUsersProgressMessage);
            var userListViewModel = new UserListViewModel(application);

            backgroundWorker.ReportProgress(0, Resources.RetrievingRolesProgressMessage); 
            var roleListViewModel = new RoleListViewModel(application);

            e.Result = new object[] { userListViewModel, roleListViewModel};
        }

        private void m_RefreshWorker_RunWorkerCompleted(
            object sender, RunWorkerCompletedEventArgs e)
        {
            if (e.Error != null)
            {
                ErrorHandler.HandleNonSpecificWCFException(e.Error, this.DisplayName);
            }
            else
            {
                Debug.Assert(!e.Cancelled);

                object[] result = e.Result as object[];
                Debug.Assert(result != null);
                this.UserListViewModel = result[0] as UserListViewModel;
                this.RoleListViewModel = result[1] as RoleListViewModel;
            }

            this.ProgressMessage = string.Empty;
            this.IsLoading = false;
        }

        private void m_AssignUsersWorker_DoWork(object sender, DoWorkEventArgs e)
        {
            BackgroundWorker backgroundWorker = sender as BackgroundWorker;
            Debug.Assert(backgroundWorker != null);

            var input = e.Argument as RoleUserInput;
            Debug.Assert(input != null);

            var roleProxy = ProxyFactory.Create<RoleManagerClient, IRoleManager>(ServiceAddressList.CredentialsService);
            var userProxy = ProxyFactory.Create<UserManagerClient, IUserManager>(ServiceAddressList.CredentialsService);

            try
            {

                int iterations = (input.SelectedRoles.Count * input.SelectedUsers.Count);
                int percentage = 0;

                for (int roleIndex = 0; roleIndex < input.SelectedRoles.Count; roleIndex++)
                {
                    for (int userIndex = 0; userIndex < input.SelectedUsers.Count; userIndex++)
                    {
                        int iteration = (roleIndex * input.SelectedUsers.Count) + (userIndex + 1);
                        percentage = iteration.CalculatePercentageOf(iterations);

                        string message = string.Format(
                            Resources.AssignUsersToRolesProgressMessage.ToString(),
                            input.SelectedUsers[userIndex], input.SelectedRoles[roleIndex],
                            percentage.ToString());
                        backgroundWorker.ReportProgress(percentage, message);

                        bool isInRole = userProxy.IsInRole(input.Application,
                            input.SelectedUsers[userIndex], input.SelectedRoles[roleIndex]);

                        if (!isInRole)
                        {
                            roleProxy.AddUserToRole(input.Application,
                                input.SelectedUsers[userIndex],
                                input.SelectedRoles[roleIndex]);
                        }
                    }
                }
            }
            finally
            {
                ProxyFactory.Cleanup<RoleManagerClient, IRoleManager>(roleProxy);
                ProxyFactory.Cleanup<UserManagerClient, IUserManager>(userProxy);
            }
        }

        private void m_RemoveUsersWorker_DoWork(object sender, DoWorkEventArgs e)
        {
            BackgroundWorker backgroundWorker = sender as BackgroundWorker;
            Debug.Assert(backgroundWorker != null);

            var input = e.Argument as RoleUserInput;
            Debug.Assert(input != null);

            var roleProxy = ProxyFactory.Create<RoleManagerClient, IRoleManager>(ServiceAddressList.CredentialsService);
            var userProxy = ProxyFactory.Create<UserManagerClient, IUserManager>(ServiceAddressList.CredentialsService);

            try
            {
                int iterations = (input.SelectedRoles.Count * input.SelectedUsers.Count);
                int percentage = 0;

                for (int roleIndex = 0; roleIndex < input.SelectedRoles.Count; roleIndex++)
                {
                    for (int userIndex = 0; userIndex < input.SelectedUsers.Count; userIndex++)
                    {
                        int iteration = (roleIndex * input.SelectedUsers.Count) + (userIndex + 1);
                        percentage = iteration.CalculatePercentageOf(iterations);

                        string message = string.Format(
                            Resources.RemoveUsersFromRolesProgressMessage.ToString(),
                            input.SelectedUsers[userIndex], input.SelectedRoles[roleIndex],
                            percentage.ToString());
                        backgroundWorker.ReportProgress(percentage, message);

                        bool isInRole = userProxy.IsInRole(input.Application,
                            input.SelectedUsers[userIndex], input.SelectedRoles[roleIndex]);

                        if (isInRole)
                        {
                            roleProxy.RemoveUserFromRole(input.Application,
                                input.SelectedUsers[userIndex],
                                input.SelectedRoles[roleIndex]);
                        }
                    }
                }

            }
            finally
            {
                ProxyFactory.Cleanup<RoleManagerClient, IRoleManager>(roleProxy);
                ProxyFactory.Cleanup<UserManagerClient, IUserManager>(userProxy);
            }
        }

        private void m_RemoveAllRolesWorker_DoWork(object sender, DoWorkEventArgs e)
        {
            BackgroundWorker backgroundWorker = sender as BackgroundWorker;
            Debug.Assert(backgroundWorker != null);

            var input = e.Argument as RoleUserInput;
            Debug.Assert(input != null);

            var roleProxy = ProxyFactory.Create<RoleManagerClient, IRoleManager>(ServiceAddressList.CredentialsService);

            try
            {
                int percentage = 0;

                for (int userIndex = 0; userIndex < input.SelectedUsers.Count; userIndex++)
                {
                    int iteration = (userIndex + 1);
                    percentage = iteration.CalculatePercentageOf(
                        input.SelectedUsers.Count);

                    string message = string.Format(
                        Resources.RemoveAllRolesFromUserProgressMessage.ToString(),
                        input.SelectedUsers[userIndex], percentage.ToString());
                    backgroundWorker.ReportProgress(percentage, message);

                    var roles = roleProxy.GetRolesForUser(input.Application,
                        input.SelectedUsers[userIndex]);

                    if (roles.Count > 0)
                    {
                        roleProxy.RemoveUserFromRoles(input.Application,
                            input.SelectedUsers[userIndex], roles);
                    }
                }

            }
            finally
            {
                ProxyFactory.Cleanup<RoleManagerClient, IRoleManager>(roleProxy);
            }
        }

        private void m_RemoveAllUsersWorker_DoWork(object sender, DoWorkEventArgs e)
        {
            BackgroundWorker backgroundWorker = sender as BackgroundWorker;
            Debug.Assert(backgroundWorker != null);

            var input = e.Argument as RoleUserInput;
            Debug.Assert(input != null);

            var roleProxy = ProxyFactory.Create<RoleManagerClient, IRoleManager>(ServiceAddressList.CredentialsService);

            try
            {
                int percentage = 0;

                for (int roleIndex = 0; roleIndex < input.SelectedRoles.Count; roleIndex++)
                {
                    int iteration = (roleIndex + 1);
                    percentage = iteration.CalculatePercentageOf(
                        input.SelectedRoles.Count);

                    string message = string.Format(
                        Resources.RemoveAllUsersFromRoleProgressMessage.ToString(),
                        input.SelectedRoles[roleIndex], percentage.ToString());
                    backgroundWorker.ReportProgress(percentage, message);

                    var users = roleProxy.GetUsersInRole(input.Application,
                        input.SelectedRoles[roleIndex]);

                    if (users.Count > 0)
                    {
                        roleProxy.RemoveUsersFromRole(input.Application, users,
                            input.SelectedRoles[roleIndex]);
                    }
                }

            }
            finally
            {
                ProxyFactory.Cleanup<RoleManagerClient, IRoleManager>(roleProxy);
            }
        }

        private void m_Worker_RunWorkerCompleted(object sender,
            RunWorkerCompletedEventArgs e)
        {
            if (e.Error != null)
            {
                ErrorHandler.HandleNonSpecificWCFException(e.Error, this.DisplayName);
            }

            this.UsersInRoleViewModel = null;
            this.RolesForUserViewModel = null;
            HandleSelectedRoleChanged(null);
            HandleSelectedUserChanged(null);
            this.ProgressMessage = string.Empty;
            this.IsLoading = false;
        }

        private void m_Worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            Debug.Assert(e.UserState != null);
            this.ProgressMessage = e.UserState.ToString();
        }

        #endregion

        #region Public properties

        public ApplicationListViewModel ApplicationListViewModel
        {
            get
            {
                return m_ApplicationListViewModel;
            }
            private set
            {
                m_ApplicationListViewModel = value;
                RaisePropertyChanged(MethodBase.GetCurrentMethod().GetPropertyName());
            }
        }

        public UserListViewModel UserListViewModel
        {
            get
            {
                return m_UserListViewModel;
            }
            private set
            {
                m_UserListViewModel = value;
                RaisePropertyChanged(MethodBase.GetCurrentMethod().GetPropertyName());
            }
        }

        public RoleListViewModel RoleListViewModel
        {
            get
            {
                return m_RoleListViewModel;
            }
            private set
            {
                m_RoleListViewModel = value;
                RaisePropertyChanged(MethodBase.GetCurrentMethod().GetPropertyName());
            }
        }

        public RolesForUserViewModel RolesForUserViewModel
        {
            get
            {
                return m_RolesForUserViewModel;
            }
            private set
            {
                m_RolesForUserViewModel = value;
                RaisePropertyChanged(MethodBase.GetCurrentMethod().GetPropertyName());
            }
        }

        public UsersInRoleViewModel UsersInRoleViewModel
        {
            get
            {
                return m_UsersInRoleViewModel;
            }
            private set
            {
                m_UsersInRoleViewModel = value;
                RaisePropertyChanged(MethodBase.GetCurrentMethod().GetPropertyName());
            }
        }

        public string CredentialsServiceLocation
        {
            get
            {
                return ServiceAddressList.CredentialsService;
            }
        }

        #endregion

        #region Private methods

        [MediatorMessageSink(MediatorMessages.SelectServiceViewModel)]
        private void HandleSelectServiceViewModel(bool message)
        {
            if (!m_SetupWorker.IsBusy && !m_RefreshWorker.IsBusy)
            {
                this.IsLoading = false;
            }
        }

        [MediatorMessageSink(MediatorMessages.RefreshApplicationList)]
        private void RefreshApplicationList(object message)
        {
            if (m_SetupWorker.IsBusy || m_RefreshWorker.IsBusy)
            {
                throw new InvalidOperationException();
            }

            RaisePropertyChanged("CredentialsServiceLocation");
            this.ApplicationListViewModel = null;
            this.RoleListViewModel = null;
            this.UserListViewModel = null;
            m_SelectedApplication = null;            
            this.ProgressMessage = Resources.RetrievingApplicationsProgressMessage;
            this.IsLoading = true;
            m_SetupWorker.RunWorkerAsync();
        }

        [MediatorMessageSink(MediatorMessages.SelectedApplicationChanged)]
        private void HandleApplicationChange(string application)
        {
            if (string.IsNullOrEmpty(application))
            {
                this.RoleListViewModel = null;
                this.UserListViewModel = null;
            }
            else
            {
                if (m_SelectedApplication == null || application != m_SelectedApplication)
                {
                    Debug.Assert(!m_RefreshWorker.IsBusy);
                    this.RoleListViewModel = null;
                    this.UserListViewModel = null;
                    m_SelectedApplication = application;
                    this.IsLoading = true;
                    m_RefreshWorker.RunWorkerAsync(application);
                }
            }
        }

        [MediatorMessageSink(MediatorMessages.ProgressMessageChanged)]
        private void HandleProgressMessageChanged(string progressMessage)
        {
            if (
                    (this.ApplicationListViewModel != null && 
                    this.ApplicationListViewModel.IsLoading) ||
                    (this.RoleListViewModel != null && this.RoleListViewModel.IsLoading) ||
                    (this.UserListViewModel != null && this.UserListViewModel.IsLoading)    
               )
            {
                this.ProgressMessage = progressMessage;
            }
        }

        [MediatorMessageSink(MediatorMessages.SelectedRoleChanged)]
        private void HandleSelectedRoleChanged(object message)
        {
            if (this.RoleListViewModel != null && this.RoleListViewModel.SelectedRoles != null)
            {
                if (this.RoleListViewModel.SelectedRoles.Count == 1)
                {
                    if (this.UsersInRoleViewModel == null ||
                        this.UsersInRoleViewModel.Role != this.RoleListViewModel.SelectedRoles[0])
                    {
                        this.UsersInRoleViewModel = new UsersInRoleViewModel(
                            m_SelectedApplication, this.RoleListViewModel.SelectedRoles[0]);
                        return;
                    }
                }
            }

            this.UsersInRoleViewModel = null;
        }

        [MediatorMessageSink(MediatorMessages.SelectedUserChanged)]
        private void HandleSelectedUserChanged(object message)
        {
            if (this.UserListViewModel != null && this.UserListViewModel.SelectedUsers != null)
            {
                if (this.UserListViewModel.SelectedUsers.Count == 1)
                {
                    if (this.RolesForUserViewModel == null ||
                        this.RolesForUserViewModel.User != this.UserListViewModel.SelectedUsers[0])
                    {
                        this.RolesForUserViewModel = new RolesForUserViewModel(
                            m_SelectedApplication, this.UserListViewModel.SelectedUsers[0]);
                        return;
                    }
                }
            }

            this.RolesForUserViewModel = null;
        }

        private bool IsRolesAndUsersSelected()
        {
            bool isRolesAndUsersSelected = false;

            if (this.RoleListViewModel != null &&
                this.RoleListViewModel.SelectedRoles != null &&
                this.RoleListViewModel.SelectedRoles.Count > 0)
            {
                if (this.UserListViewModel != null &&
                    this.UserListViewModel.SelectedUsers != null &&
                    this.UserListViewModel.SelectedUsers.Count > 0)
                {
                    isRolesAndUsersSelected = true;
                }
            }

            return isRolesAndUsersSelected;
        }

        private void RunWorker(BackgroundWorker backgroundWorker, string confirmMessage)
        {
            if (this.IsLoading)
            {
                throw new InvalidOperationException();
            }

            Debug.Assert(!string.IsNullOrEmpty(m_SelectedApplication));
            Debug.Assert(!backgroundWorker.IsBusy);

            IMessageBoxService messageBox = base.GetService<IMessageBoxService>();
            Debug.Assert(messageBox != null);

            MessageBoxResult result = messageBox.Show(confirmMessage, this.DisplayName,
                MessageBoxButton.YesNo, MessageBoxImage.Question);

            if (result == MessageBoxResult.Yes)
            {
                var input = new RoleUserInput()
                {
                    Application = m_SelectedApplication,
                    SelectedRoles = this.RoleListViewModel.SelectedRoles,
                    SelectedUsers = this.UserListViewModel.SelectedUsers
                };

                this.IsLoading = true;
                backgroundWorker.RunWorkerAsync(input);
            }
        }

        private void AssignUsersToRoles()
        {
            bool isRolesAndUsersSelected = IsRolesAndUsersSelected();

            if (isRolesAndUsersSelected)
            {
                RunWorker(m_AssignUsersWorker,
                    Resources.ConfirmAssignUsersToRolesMessage.ToString());
            }
        }

        private void RemoveUsersFromRoles()
        {
            bool isRolesAndUsersSelected = IsRolesAndUsersSelected();

            if (isRolesAndUsersSelected)
            {
                RunWorker(m_RemoveUsersWorker,
                    Resources.ConfirmRemoveUsersFromRolesMessage.ToString());
            }
        }

        private void RemoveAllRolesFromUsers()
        {
            if (this.UserListViewModel != null &&
                this.UserListViewModel.SelectedUsers != null &&
                this.UserListViewModel.SelectedUsers.Count > 0)
            {
                RunWorker(m_RemoveAllRolesWorker,
                    Resources.ConfirmRemoveAllRolesFromUsersMessage.ToString());
            }
        }

        private void RemoveAllUsersFromRoles()
        {
            if (this.RoleListViewModel != null &&
                this.RoleListViewModel.SelectedRoles != null &&
                this.RoleListViewModel.SelectedRoles.Count > 0)
            {
                RunWorker(m_RemoveAllUsersWorker,
                    Resources.ConfirmRemoveAllUsersFromRolesMessages.ToString());
            }
        }

        #endregion

        #region Commands

        public ICommand AssignUsersToRolesCommand
        {
            get
            {
                return m_AssignUsersToRolesCommand ?? (m_AssignUsersToRolesCommand = new RelayCommand(
                    param => this.AssignUsersToRoles()));
            }
        }

        public ICommand RemoveUsersFromRolesCommand
        {
            get
            {
                return m_RemoveUsersFromRolesCommand ?? (m_RemoveUsersFromRolesCommand = new RelayCommand(
                    param => this.RemoveUsersFromRoles()));
            }
        }

        public ICommand RemoveAllRolesFromUsersCommand
        {
            get
            {
                return m_RemoveAllRolesFromUsersCommand ?? (m_RemoveAllRolesFromUsersCommand = new RelayCommand(
                    param => this.RemoveAllRolesFromUsers()));
            }
        }

        public ICommand RemoveAllUsersFromRolesCommand
        {
            get
            {
                return m_RemoveAllUsersFromRolesCommand ?? (m_RemoveAllUsersFromRolesCommand = new RelayCommand(
                    param => this.RemoveAllUsersFromRoles()));
            }
        }

        #endregion

    }
}
